package ru.vsu.amm.input;

import static java.lang.System.*;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Генератор граничных условий для тестовых примеров.
 * @author Иванов Илья
 * @since 12 января 2013
 *
 */
public class BoundaryConditionsGenerator {
	
	private static final String[] KEYS = {
		"-l", 	// left side
		"-r", 	// right side
		"-n", 	// near side
		"-f", 	// far side
		"-b", 	// bottom side
		"-t" 	// top side
	};
	private static final Set<String> PARAMETERS_KEYS = new HashSet<>(Arrays.asList(KEYS));
	
	private int testNumber;
	private Map<String, Double> parameters = new HashMap<>();
	private boolean isParametersValid;
	
	private BoundaryConditionsGenerator(String paramsString) {
		isParametersValid = readParams(paramsString);
	}
	
	private boolean readParams(String paramsString) {
		String[] params = paramsString.split(" ");
		if (params.length == 0)
			return false;
		
		int argIndex = 0;
		try {
			testNumber = Integer.valueOf(params[argIndex++]);
		} catch (NumberFormatException e) {
			return false;
		}
		
		int isKey = argIndex % 2;
		String key = null;
		for (; argIndex < params.length; argIndex++)
			if (argIndex % 2 == isKey) { // Читаем ключ
				if (!PARAMETERS_KEYS.contains(params[argIndex]))
					return false;
				
				key = params[argIndex];
			} else // Читаем значение
				try {
					double value = Double.parseDouble(params[argIndex]);
					parameters.put(key, value);
				} catch (NumberFormatException e) {
					return false;
				}
		
		if (argIndex % 2 != isKey) // Если последний аргумент - ключ, параметры неверны
			return false;
		
		return true;
	}
	
	private boolean generate() {
		if (!isParametersValid)
			return false;
		
		Input in = new Input(testNumber, false);
		if (!in.isValid())
			return false;
		
		Map<int[][], double[]> parsedParams = new HashMap<>();
		for (Entry<String, Double> p : parameters.entrySet())
			if (KEYS[0].equals(p.getKey()))
				parsedParams.put(new int[][] { {0, 0}, {0, in.Ny}, {0, in.Nz}}, new double[] { p.getValue(), 0, 0});
			else if (KEYS[1].equals(p.getKey()))
				parsedParams.put(new int[][] { {in.Nx, in.Nx}, {0, in.Ny}, {0, in.Nz}}, new double[] { p.getValue(), 0, 0});
			else if (KEYS[2].equals(p.getKey()))
				parsedParams.put(new int[][] { {0, in.Nx}, {0, 0}, {0, in.Nz}}, new double[] { 0, p.getValue(), 0});
			else if (KEYS[3].equals(p.getKey()))
				parsedParams.put(new int[][] { {0, in.Nx}, {in.Ny, in.Ny}, {0, in.Nz}}, new double[] { 0, p.getValue(), 0});
			else if (KEYS[4].equals(p.getKey()))
				parsedParams.put(new int[][] { {0, in.Nx}, {0, in.Ny}, {0, 0}}, new double[] { 0, 0, p.getValue()});
			else if (KEYS[5].equals(p.getKey()))
				parsedParams.put(new int[][] { {0, in.Nx}, {0, in.Ny}, {in.Nz, in.Nz}}, new double[] { 0, 0, p.getValue()});
		
		String boundaryConditionsFileName = Input.generateBoundaryConditionsFileName(testNumber);
		try (BufferedWriter wrt = new BufferedWriter(new FileWriter(boundaryConditionsFileName))) {
			writeTest(parsedParams, in, wrt);
		} catch (IOException e) {
			return false;
		}
		
		return true;
	}
	
	private static void writeTest(Map<int[][], double[]> parsedParams, Input in, BufferedWriter wrt) throws IOException {
		for (Entry<int[][], double[]> p : parsedParams.entrySet()) {
			int iS = p.getKey()[0][0], iF = p.getKey()[0][1];
			int jS = p.getKey()[1][0], jF = p.getKey()[1][1];
			int kS = p.getKey()[2][0], kF = p.getKey()[2][1];
			for (int i = iS; i <= iF; i++)
				for (int j = jS; j <= jF; j++)
					for (int k = kS; k <= kF; k++) {
						wrt.write(String.format(Locale.US, "%1$d %2$f %3$f %4$f", n(in, i, j, k), p.getValue()[0], p.getValue()[1], p.getValue()[2]));
						wrt.newLine();
					}
		}
	}
	
	private static int n(Input in, int i, int j, int k) {
		return (in.Nz + 1) * (in.Ny + 1) * i + (in.Nz + 1) * j + k;
	}
	
	public static void main(String[] args) {
		printDescription();
		out.println("Введите параметры:");
		
		String params = null;
		try (BufferedReader rdr = new BufferedReader(new InputStreamReader(in))) {
			params = rdr.readLine();
		} catch (IOException e) {}
		if (params == null) {
			err.println("Ошибка ввода-вывода");
			return;
		}
		
		BoundaryConditionsGenerator gen = new BoundaryConditionsGenerator(params);
		if (gen.generate())
			out.println("Граничные условия успешно сгенерированы!");
		else
			out.println("Ошибка генерации граничных условий");
	}
	
	/*
	public static void main(String[] args) {
		generateWaveTest(4);
	}
	*/
	
	private static void printDescription() {
		out.println("Генератор граничных условий. Параметры:");
		out.println();
		out.println("num [-r val ][-l val ][-n val ][-f val ][-b val ][-t val ]");
		out.println("num - номер теста; определяет файл с параметрами разбиения");
		out.println("-l - левая");
		out.println("-r - правая");
		out.println("-n - ближняя");
		out.println("-f - дальняя");
		out.println("-b - нижняя");
		out.println("-t - верхняя грани параллелепипеда");
		out.println("val - значение перемещения грани в перепендекулярном к неё направлении");
		out.println("Наблюдатель находится лицом к грани (0, 0, 0) -> (Nx, 0, 0) -> (Nx, 0, Nz) -> (0, 0, Nz).");
		out.println();
	}
	
	@SuppressWarnings("unused")
	private static void generateWaveTest(int testNumber) {
		Input in = new Input(testNumber, false);
		if (in == null || !in.isValid())
			return;
		
		float h = -0.01f;
		float l = -0.0025f;
		int k1 = Math.round(in.Nx * 0.25f);
		int k2 = in.Nx - k1;
		float dz1 = (l - h) / k1;
		float dz2 = (l - h) / k2;
		
		try (BufferedWriter wrt = new BufferedWriter(new FileWriter(Input.generateBoundaryConditionsFileName(testNumber)))) {
			int i = 0;
			
			for (; i <= k1; i++) {
				float z = h + i * dz1;
				for (int j = 0; j <= in.Ny; j++) {
						wrt.write(String.format(Locale.US, "%1$d %2$f %3$f %4$f", n(in, i, j, in.Nz), 0f, 0f, z));
						wrt.newLine();
				}
			}
			
			for (int i1 = k2; i1 >= 0; i1--, i++) {
				float z = h + i1 * dz2;
				for (int j = 0; j <= in.Ny; j++) {
					wrt.write(String.format(Locale.US, "%1$d %2$f %3$f %4$f", n(in, i, j, in.Nz), 0f, 0f, z));
					wrt.newLine();
				}
			}
			
			for (i = 0; i <= in.Nx; i++)
				for (int j = 0; j <= in.Ny; j++) {
					wrt.write(String.format(Locale.US, "%1$d %2$f %3$f %4$f", n(in, i, j, 0), 0f, 0f, 0f));
					wrt.newLine();
				}
		} catch (IOException e) {}
	}
}